module JSRake

  class FileList
    
    include Enumerable
    
    def initialize()
      @files = Hash.new
    end
    
    def each
      @files.each_value { |file| file.rewind; yield( file ) }      
    end
    
    def each_pair
      @files.each_pair { |filename, file| file.rewind; yield( filename, file ) }
    end
    
    def [] (filename)
      @files[filename]
    end
    
    def has_filename?(filename)
      @files.has_key? filename
    end
    
  protected
    
    def files
      @files
    end

  end

  # Provides a FileList which includes files that match a specific
  # glob pattern from a given base path
  class PatternFileList < FileList
    
    include Enumerable
    
    def initialize(base_path, file_pattern)
      
      super()
      
      Dir.glob( File.join(base_path, file_pattern) ).each do |script_filename|
        self.files[script_filename] = File.open(script_filename)
      end
      
    end
    
  end
  
  class TopoSortedFileList < FileList
    
    include Enumerable
    
    def initialize(file_list)
      super()
      
      @dependencies = Hash.new
      @dependents = Hash.new
      @file_order = []
      
      @parent_file_list = file_list
      parse_dependencies(file_list)
      topo_sort()
    end
    
    
    def each
      @file_order.each do | filename |
        yield(@parent_file_list[filename])
      end
    end
    
    def each_pair
      @file_order.each do |filename|
        yield(filename, @parent_file_list[filename])
      end
    end
    
    def [] (filename)
      @parent_file_list[filename]
    end
    
  private
  
  
    # Gather dependinces listed in the javascript files
    def parse_dependencies(file_list)
      
      # Initialize the dependency lists
      @parent_file_list.each_pair do |filename, file|
        @dependencies[filename] = []
        @dependents[filename] = []
      end
      
      require_line_pattern = /\/\*\s*#require\s+(?:"|')(.*)(?:"|')\s*\*\//
      javascript_base = File.join( "app", "javascripts" )
      
      file_list.each_pair do |filename, file|

        line_number = 1

        while (next_line = file.gets)

          require_line_matcher = next_line.match require_line_pattern

          unless require_line_matcher.nil?
            
            # Use the absolute path of the file.
            required_file = File.join(RAILS_ROOT, javascript_base, require_line_matcher[1])
            
            raise "FATAL: #{required_file} not included in parent FileList. (#{filename}:#{line_number})" unless @parent_file_list.has_filename? required_file
            
            @dependencies[filename].push required_file
            @dependents[required_file].push filename

          end

          line_number += 1

        end
        
        # Rewind the opened files for later reading...
        file.rewind

      end
      
    end
    
    # Sorts javascript files by dependency
    def topo_sort()
      
      # A list of sorted nodes that will be built
      sorted_nodes = []
      
      # Free nodes are nodes without incoming dependencies
      free_nodes = @dependents.keys.select { |filename| @dependents[filename].empty? }
      
      @dependents_temp = @dependents.clone
      
      while free_nodes.empty? == false
        
        node_to_remove = free_nodes.pop
        sorted_nodes.push node_to_remove
        
        if @dependencies.has_key? node_to_remove
        
          # Remove outgoing dependencies for node_to_remove and add any free nodes that result
          @dependencies[node_to_remove].each do |dep_node|
          
            @dependents_temp[dep_node] = @dependents_temp[dep_node].reject { |dependent| dependent == node_to_remove }
            
            free_nodes.push dep_node if @dependents_temp[dep_node].size == 0
            
          end
          
        end
        
      end
            
      @file_order = sorted_nodes.reverse
      
    end
    
  end
  
  
  class JSFileConcatenator
        
    def initialize(file_list)
      @file_list = file_list
    end
    
    def write_to(output_file, options = {})
      
      @file_list.each_pair do |filename, file|
        
        # Mark file beginning
        output_file.puts "/* BEGIN #{filename} */"
        output_file.puts ""
        
        line_number = 1
        while (line = file.gets)
          output_file.puts( ("\t" * 1) + "/*#{line_number}*/" + ("\t" * 2) + line )
          line_number += 1
        end
        
        output_file.puts ""
        
        # Rewind the opened files for later reading...
        file.rewind
                
      end
    end
    
  end

end